﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Timers;
using System.Diagnostics;
using System.Timers;

namespace FunctionScope.Models
{
    public class FunctionSamplerModel : IDisposable
    {

        public static readonly int FS = 5000;

        public ISampleable _xFunction;
        public ISampleable _yFunction;

        public TraceModel<double> Trace { get; private set; }

        public long _refTime;
        public double _lastSampleTime;

        public bool Running { get; set; }

        private System.Timers.Timer _sampleTimer;

        private Stopwatch _timeBase;
        private double _timeBaseFreq;

        public FunctionSamplerModel(ISampleable xFunc, ISampleable yFunc)
        {

            //Create a trace for enough samples for 1 seconds at FS Fs
            Trace = new TraceModel<double>(5008);

            _xFunction = xFunc;
            _yFunction = yFunc;

            //Inherit scales from function
            Trace.MinX = _xFunction.Min;
            Trace.MaxX = _xFunction.Max;
            Trace.MinY = _xFunction.Min;
            Trace.MaxY = _xFunction.Max;

            //Setup time base
            _timeBase = new Stopwatch();
            _timeBase.Start();
            _timeBaseFreq = Stopwatch.Frequency;

            _refTime = _timeBase.ElapsedTicks;

            //Setup a FS Hz sampler. Check resolution / jitter of this later...
            _sampleTimer = new System.Timers.Timer(10);
            _sampleTimer.Elapsed += SampleTimer;
            _sampleTimer.Start();

            Running = true;

        }

        private void SampleTimer(object o, EventArgs e)
        {
            //Due to inaccurate timer resolution see what time we actually woke up and roll back through any samples there were missed

            lock (Trace.SyncRoot)
            {

                double simTime = (_timeBase.ElapsedTicks - _refTime) / _timeBaseFreq;

                while (_lastSampleTime <= simTime)
                {
                    _lastSampleTime += 1.0 / (float)FS;
                    Sample(_lastSampleTime);
                }

                //Rebase double, this prevents accumulated errors
                _lastSampleTime = simTime;

            }

        }

        /// <summary>
        /// Sets the trace length in Milliseconds for the given sample rate, rounding up to the nearest clip point
        /// </summary>
        /// <param name="ms"></param>
        public int SetTraceSize(int ms, int clip)
        {
            int targetSize = (int)(FS * ((double)ms / 1000.0d));

            //Clip to nearest
            targetSize -= targetSize % clip;

            if (targetSize == 0)
                targetSize = clip;

            lock (Trace.SyncRoot)
            {
                if (Trace.Length != targetSize)
                {
                    Trace.ResetTraceSize(targetSize);
                }
            }

            return (targetSize * 1000) / FS;
        }

        private void Sample(double t)
        {
            //Sample x,y functions to generate a point
            Trace.Push(_xFunction.Sample(t), _yFunction.Sample(t), t);
        }

        public void Dispose()
        {
            Running = false;
        }
    }
}
